#!/usr/bin/env python
# 
# User: bob
# Date: 02/12/14
# Copyright (C) 2013 by G3UKB Bob Cowdery
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#    
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#    
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#    
#  The author can be reached by email at:   
#     bob@bobcowdery.plus.com
#

# System imports
import os,sys
from PyQt4 import QtCore, QtGui
import pickle
import urllib
import traceback
import subprocess
from time import sleep
import logging

# Application imports
import common.instance_cache as instance_cache
from common.defs import *
from common import persist
from common import broker
from ui.transmitter.tx_common import *
from ui.common import meter
from ui.common import vfo

"""
GUI class for a transmitter
"""
class Transmitter(QtGui.QMainWindow):
    
    def __init__(self, tx_id, ch_api, q, channels):
        """
        Constructor
        
        Arguments:
            tx_id       -- the id of this transmitter
            ch_api      -- the channel API object for this receiver
            q           -- Q to send messages to
            channels    -- {CH_RX: {rx-id:dsp-api-inst, rx-id:dsp-api-inst}, CH_TX: {tx_id:dsp-api-inst}}
            
        """
        
        super(Transmitter, self).__init__()
        
        self.__q = q
        self.__tx_id = tx_id
        self.__ch_api = ch_api
        self.__channels = channels
        self.__monitor = False
         
        self.STATE_FILE = 'tx.state_%d' % (tx_id)
        
        #-----------------------------------------------------------------------
        # Get the default or saved state
        self.__state = persist.getSavedState(self.STATE_FILE)
        if self.__state == None:
            # Apply defaults
            self.__state = DEFAULT_STATE
            persist.saveState(self.STATE_FILE, self.__state)
        self.__current_freq = self.__state[FREQ]

        # Get the current settings
        self.__cfg = instance_cache.getInstance('Settings')
        self.__current_cfg = self.__cfg.copy()
        
        # Get our API instance
        self.__metis_api = instance_cache.getInstance('Metis_API')
        # and the controller instance
        self.__ctrl = instance_cache.getInstance('Controller')
               
        # Set the back colour
        palette = QtGui.QPalette()
        palette.setColor(QtGui.QPalette.Background,QtGui.QColor(61,87,88,255))
        self.setPalette(palette)
        
        # Initialise the GUI
        self.initUI(tx_id)
        
        # Initialise variables
        # Starts in RX mode
        self.__mode = CH_RX
        
        # Subscribe to events
        broker.subscribe(self, RX_0_FREQ_EVNT)
        broker.subscribe(self, CONFIG_EVNT)
        
        # Timer for background tasks
        self.__timer = QtCore.QTimer()
        self.__timer.setInterval(100)
        self.__timer.timeout.connect(self.on_timeout)
        self.__timer.start()
        
    def initUI(self, tx_id):
        
        try:
            self.setToolTip('SdrScript TX Interface [%d]' % (tx_id))
            QtGui.QToolTip.setFont(QtGui.QFont('SansSerif', 10))
            
            # Arrange the window
            self.move(self.__state[WINDOW_X], self.__state[WINDOW_Y])
            if self.__state[WINDOW_WIDTH] == -1 or self.__state[WINDOW_HEIGHT] == -1:
                self.__state[WINDOW_WIDTH] = self.__width = self.width()
                self.__state[WINDOW_HEIGHT] = self.__height = self.height()
            else:
                self.__width = self.__state[WINDOW_WIDTH]
                self.__height = self.__state[WINDOW_HEIGHT]
                self.resize(self.__width, self.__height)
            
            self.setWindowTitle('SdrScript Transmitter')
            # Turn off all title bar buttons
            self.setWindowFlags(QtCore.Qt.Tool | QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint)
        
            # Main panel and grid
            w = QtGui.QWidget()
            self.setCentralWidget(w)
            grid = QtGui.QGridLayout()
            w.setLayout(grid)
            button_grid = QtGui.QGridLayout()
            grid.addLayout(button_grid, 0, 0)
            main_grid = QtGui.QGridLayout()
            grid.addLayout(main_grid, 0, 1)
            
            # Add buttons
            # Compact towards top with no spacing
            button_grid.setRowStretch(3,10)
            button_grid.setColumnStretch(0,10)
            button_grid.setSpacing(0)
            # MOX
            self.__mox_btn = QtGui.QPushButton('MOX')
            self.__mox_btn.setCheckable(True)
            self.__mox_btn.setStyleSheet("QPushButton {background-color: rgb(46,84,35);  font: 12px}")
            button_grid.addWidget(self.__mox_btn, 0, 0)
            self.__mox_btn.toggled.connect(self.on_mox)
            # Tune
            self.__tune_btn = QtGui.QPushButton('Tune')
            self.__tune_btn.setCheckable(True)
            self.__tune_btn.setStyleSheet("QPushButton {background-color: rgb(100,141, 142);  font: 12px}")
            button_grid.addWidget(self.__tune_btn, 1, 0)
            self.__tune_btn.toggled.connect(self.on_tune)
            # Monitor
            self.__monitor_btn = QtGui.QPushButton('Monitor')
            self.__monitor_btn.setCheckable(True)
            self.__monitor_btn.setStyleSheet("QPushButton {background-color: rgb(100,141, 142);  font: 12px}")
            button_grid.addWidget(self.__monitor_btn, 2, 0)
            self.__monitor_btn.toggled.connect(self.on_monitor)

            # Add meter control
            self.__meter = meter.Meter(w, CH_TX, M_TX_OUT_AV)
            main_grid.addWidget(self.__meter, 0, 0, -1, -1)
            main_grid.setRowMinimumHeight(0, 15)

            # Add VFO control
            vfo_grid = QtGui.QGridLayout()
            main_grid.addLayout(vfo_grid, 1, 0)
            self.__vfo = vfo.Vfo(self.__ctrl, self.__metis_api, CH_TX, tx_id)
            self.__vfo.addVfo(self, vfo_grid, self.__current_freq)
            
            # Add mode, filter and AGC controls
            ancilliary_panel = QtGui.QWidget(w)
            main_grid.addWidget(ancilliary_panel, 2, 0)
            self.__addAncilliary(ancilliary_panel)
            
            # Gain control
            gain_control_panel = QtGui.QWidget(w)
            main_grid.addWidget(gain_control_panel, 3, 0)
            self.__addGainControls(gain_control_panel)
            
        except Exception as e:
            self.__q.put ((BOTH, logging.ERROR, 'py.tx_ui', 'Exception [%s][%s]' % (str(e), traceback.format_exc())))
    
    def ensure_rx(self):
        """ Stopping so don't stop in TX mode """
        
        if self.__mode == CH_TX:
            # Set state RX
            self.__mox_btn.setStyleSheet("QPushButton {background-color: rgb(46,84,35);  font: bold 12px}")
            self.__set_rx_tx_mode(CH_RX, self.__channels, False)
            self.__mox_btn.setChecked(False)
            self.__tune_btn.setChecked(False)
            self.__monitor_btn.setChecked(False)
            self.__monitor = False
    
    def closedown(self):
        """ Request to close """
        
        # Update window position
        position = self.pos()
        self.__state[WINDOW_X] = position.x()
        self.__state[WINDOW_Y] = position.y()
        self.__state[WINDOW_WIDTH] = self.width()
        self.__state[WINDOW_HEIGHT] = self.height()
        # and frequency
        self.__state[FREQ] = self.__current_freq
        # Save to the state file
        persist.saveState(self.STATE_FILE, self.__state)
        
        # Quit the window
        self.close()
    
    def updateConfig(self):
        """ Update the backend """
        
        self.__metis_api.set_tx_freq(self.__tx_id, self.__current_freq)
        self.__ch_api.set_tx_gain(float(self.__state[GAIN])/100.0)
    
    def run(self):
        """ Show the window """
          
        self.show()
    
    # UI Actions ---------------------------------------------------------------
    def on_mox(self, checked):
        """
        MOX button event
        
        Arguments:
            checked   --  True if checked
            
        """
        
        if checked:
            # Set state TX
            self.__mox_btn.setStyleSheet("QPushButton {background-color: rgb(140,21,38);  font: bold 12px}")
            self.__set_rx_tx_mode(CH_TX, self.__channels, self.__monitor)
            self.__mode = CH_TX
        else:
            # Set state RX
            self.__mox_btn.setStyleSheet("QPushButton {background-color: rgb(46,84,35);  font: bold 12px}")
            self.__set_rx_tx_mode(CH_RX, self.__channels, self.__monitor)
            self.__mode = CH_RX
    
    def on_tune(self, checked):
        """
        Tune button event
        
        Arguments:
            checked   --  True if checked
            
        """
        
        if checked:
            # Set state TX
            self.__ch_api.set_tx_gain(float(self.__cfg[TX][LEVELS][TUNE_DRIVE_LEVEL])/100.0)
            self.__tune_btn.setStyleSheet("QPushButton {background-color: rgb(140,21,38);  font: bold 12px}")
            self.__set_rx_tx_mode(CH_TX, self.__channels, self.__monitor)
            self.__mode = CH_TX
        else:
            # Set state RX
            self.__ch_api.set_tx_gain(float(self.__state[GAIN])/100.0)
            self.__tune_btn.setStyleSheet("QPushButton {background-color: rgb(100,141, 142);  font: bold 12px}")
            self.__set_rx_tx_mode(CH_RX, self.__channels, self.__monitor)
            self.__mode = CH_RX
        
    def on_monitor(self, checked):
        """
        Monitor button event
        
        Arguments:
            checked   --  True if checked
            
        """
        
        if checked:
            self.__monitor_btn.setStyleSheet("QPushButton {background-color: rgb(140,21,38);  font: bold 12px}")
        else:
            self.__monitor_btn.setStyleSheet("QPushButton {background-color: rgb(100,141, 142);  font: bold 12px}")
            
        self.__monitor = checked
        self.__set_rx_tx_mode(CH_MON, self.__channels, self.__monitor)
              
    def wheelEvent(self, event):
        """
        Mouse wheel event
        
        Arguments:
            event   --  event data
            
        """
        
        super(Transmitter, self).wheelEvent(event)
        
        self.__current_freq = self.__vfo.wheelEvent(event.delta())
    
    def on_mode(self, sel):
        """
        Mode change
        
        Arguments:
            event   --  event data
            
        """
     
        self.__state[MODE] = sel
        self.__ch_api.set_tx_mode(self.__state[MODE])
        self.__set_filter()
        
    def on_filter(self, sel):
        """
        Filter change
        
        Arguments:
            event   --  event data
            
        """
        
        self.__state[FILTER] = sel
        self.__set_filter()
    
    def on_gain(self, value):
        """
        Gain change
        
        Arguments:
            event   --  event data
            
        """
        
        self.__state[GAIN] = value
        self.__ch_api.set_tx_gain(float(value)/100.0)

    def on_timeout(self):
        """
        Background processing
        
        Arguments:
            
        """
        
        db = self.__ch_api.get_tx_meter_data(M_TX_OUT_AV)
        if self.__meter != None: self.__meter.update_meter(db)
        
    # PRIVATE ==================================================================
    
    # Interactors -------------------------------------------------------------- 
    def __addAncilliary(self, panel):
        """
        Add ancilliary controls:
            Mode
            Filter
            AGC
        
        Arguments:
            panel   -- bounding area for controls
            
        """    
        
        # Layout
        grid = QtGui.QGridLayout()
        grid.setColumnMinimumWidth(0, 80)
        grid.setColumnMinimumWidth(1, 80)
        grid.setColumnStretch(0,1)
        grid.setColumnStretch(1,1)
        panel.setStyleSheet(".QWidget {border-style: solid; border-color: gray; border-width: 1px;}")
        
        # Mode
        mode_label = QtGui.QLabel('Mode')
        grid.addWidget(mode_label, 0, 0, QtCore.Qt.AlignCenter)
        mode_combo = QtGui.QComboBox()
        mode_list = []
        for mode in mode_lookup:
            mode_list.append(mode[1])
        mode_combo.addItems(mode_list)
        grid.addWidget(mode_combo, 1, 0)
        mode_combo.activated.connect(self.on_mode)
        # Set the current state
        mode_combo.setCurrentIndex(self.__state[MODE])
        self.__ch_api.set_tx_mode(self.__state[MODE])
        
        # Filter
        filter_label = QtGui.QLabel('Filter')
        grid.addWidget(filter_label, 0, 1, QtCore.Qt.AlignCenter)
        filter_combo = QtGui.QComboBox()
        filter_list = []
        for filter in filter_lookup:
            filter_list.append(filter[3])
        filter_combo.addItems(filter_list)
        grid.addWidget(filter_combo, 1, 1)
        filter_combo.activated.connect(self.on_filter)
        # Set the current state
        filter_combo.setCurrentIndex(self.__state[FILTER])
        self.__ch_api.set_tx_filter_run(CH_BP_ON)
        self.__set_filter()
        
        panel.setLayout(grid)
    
    def __addGainControls(self, panel):
        """
        Add gain controls:
            Drive
        
        Arguments:
            panel   -- bounding area for controls
            
        """    
        
        # Layout
        grid = QtGui.QGridLayout()
        panel.setStyleSheet(".QWidget {border-style: solid; border-color: gray; border-width: 1px;}")
        
        # Drive
        next_grid = QtGui.QGridLayout()
        gain_label = QtGui.QLabel('Drive')
        next_grid.addWidget(gain_label, 0, 0)
        gain_slider = QtGui.QSlider(QtCore.Qt.Horizontal)
        gain_slider.setTickInterval(50)
        next_grid.addWidget(gain_slider, 0, 1)
        gain_slider.setValue (self.__state[GAIN])
        gain_slider.valueChanged.connect(self.on_gain)
        grid.addLayout(next_grid, 1, 0)
        
        panel.setLayout(grid)

    # Helpers --------------------------------------------------------------     
    def __set_filter(self):
        """
        Set the filter according to mode
        
        """
        
        mode = self.__state[MODE]
        filter = self.__state[FILTER]
        (_, low, high, _) = filter_lookup[filter]
        lsb = (CH_LSB, CH_CWL, CH_DIGL)
        wb = (CH_DSB, CH_FM, CH_AM, CH_SAM)
        if mode in lsb:
            low_cutoff = -high
            high_cutoff = -low
        elif mode in wb:
            low_cutoff = -high
            high_cutoff = high
        else:
            low_cutoff = low
            high_cutoff = high
            
        self.__ch_api.set_tx_filter_freq(low_cutoff, high_cutoff)       
    
    def __set_rx_tx_mode(self, mode, channels, monitor=False):       
        """
        Set the mode to RX or TX
        
        Arguments:
            mode        --  CH_RX | CH_TX | CH_MON
            channels    --  {CH_RX: {rx-id:dsp-api-inst, rx-id:dsp-api-inst}, CH_TX: {tx_id:dsp-api-inst}}
            monitor     --  True if transmission monitoring required
        
        """

        if mode == CH_TX:
            # Switch to transmit mode
            # Downslew all receivers except RX_0 which is the monitor receiver if monitor is on            
            if monitor: start_rx = 1    # i.e. Leave RX_0 on
            else: start_rx = 0            
            for n in range(start_rx, len(channels[CH_RX])):
                if n == len(channels[CH_RX])-1:
                    # We wait only for the last receiver to minimise the switch over delay
                    # However CH_TRANSITION_WAIT seems to blow up at the moment
                    channels[CH_RX][n].set_ch_state(CH_STATE_STOP, CH_TRANSITION_NOWAIT)
                else:
                    channels[CH_RX][n].set_ch_state(CH_STATE_STOP, CH_TRANSITION_NOWAIT)
            # Switch Metis to TX                       
            self.__metis_api.set_mox(True)
            # Start the TX DSP channel
            channels[CH_TX][0].set_ch_state(CH_STATE_START, CH_TRANSITION_NOWAIT)
        elif mode == CH_RX:
            # Switch to receive mode
            # Downslew the transmitter
            channels[CH_TX][0].set_ch_state(CH_STATE_STOP, CH_TRANSITION_NOWAIT)
            # Switch Metis to RX 
            self.__metis_api.set_mox(False)
            # Slew up receivers
            if monitor: start_rx = 1    # i.e. Leave RX_0 on
            else: start_rx = 0  
            for n in range(start_rx, len(channels[CH_RX])):
                channels[CH_RX][n].set_ch_state(CH_STATE_START, CH_TRANSITION_NOWAIT)
        elif mode == CH_MON and self.__mode == CH_TX:
            # Change in monitor state
            if monitor:
                # Upslew RX_0
                channels[CH_RX][0].set_ch_state(CH_STATE_START, CH_TRANSITION_NOWAIT)
            else:
                # Downslew RX_0
                channels[CH_RX][0].set_ch_state(CH_STATE_STOP, CH_TRANSITION_NOWAIT)               
 
    # Events -------------------------------------------------------------------
    def process_event(self, event_type, event_data):
        
        if event_type == RX_0_FREQ_EVNT:
            self.__current_freq = event_data
            self.__vfo.set_freq(self.__current_freq)
        elif event_type == CONFIG_EVNT:
            self.__cfg = instance_cache.getInstance('Settings')
        